package ws.jdbc.concurrent.driver.sqlserver.tds.datatypes;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.sql.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import ws.utils.ArrayUtil;

/**
 * Byte stream for variable-length type.
 * 
 * <p><a href="http://msdn.microsoft.com/en-us/library/dd340469(v=PROT.13).aspx">Reference</a></p>
 *
 * @author Wilson Santos, 36228 <wilson.santos@ua.pt>
 */
public class TYPE_VARBYTE {

  private TYPE_VARLEN type_varlen = null;
  private ByteBuffer dataBuffer = null;
  /** 16-bit UCS Transformation Format, little-endian byte order. */
  private static final String charsetName = "UTF-16LE";

  private TYPE_VARBYTE(TYPE_VARLEN type_varlen) {
    this.type_varlen = type_varlen;
  }

  public TYPE_VARBYTE(byte[] data) {
    this.dataBuffer = ByteBuffer.allocate(data.length).order(ByteOrder.LITTLE_ENDIAN).put(data);
  }

  public TYPE_VARBYTE(TYPE_VARLEN type_varlen, byte[] data) {
    this(type_varlen);
    if (data != null) {
      this.dataBuffer = ByteBuffer.allocate(data.length).order(ByteOrder.LITTLE_ENDIAN).put(data);
    }
  }

  public TYPE_VARBYTE(int data) {
    this(TYPE_VARLEN.create(VARLENTYPE.INTTYPE, 4));
    this.dataBuffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(data);
  }

  public TYPE_VARBYTE(long data) {
    this(TYPE_VARLEN.create(VARLENTYPE.INTTYPE, 8));
    this.dataBuffer = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(data);
  }

  public TYPE_VARBYTE(String data) {
    this(TYPE_VARLEN.create(VARLENTYPE.NVARCHARTYPE, 2 * data.length()));
    this.dataBuffer = ByteBuffer.allocate(2 * data.length()).put(ArrayUtil.toLittleEndian(data));
  }

  public TYPE_VARBYTE(double data) {
    this(TYPE_VARLEN.create(VARLENTYPE.FLTNTYPE, 8));
    this.dataBuffer = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putDouble(data);
  }

  public TYPE_VARLEN getTypeVarlen() {
    return type_varlen;
  }

  public byte[] getBytes() {
    if (dataBuffer != null) {
      return ByteBuffer.allocate(size()).order(ByteOrder.LITTLE_ENDIAN).
              put(type_varlen.getBytes()).
              put(dataBuffer.array()).
              array();
    } else {
      return null;
    }
  }

  /**
   * Get size in bytes.
   *
   * @return size in bytes.
   */
  public int size() {
    return type_varlen.size() + dataBuffer.array().length;
  }

  /**
   * TYPE_VARBYTE string representation.
   *
   * @return TYPE_VARBYTE string representation.
   */
  @Override
  public String toString() {
    if (type_varlen != null) {
      if (dataBuffer != null) {
        return "TYPE_VARBYTE{"
                + "type_varlen=" + type_varlen
                + ",  dataBuffer=" + ArrayUtil.toHexadecimal(dataBuffer.array())
                + "}";
      } else {
        return "TYPE_VARBYTE{NULL}";
      }
    } else {
      return "TYPE_VARBYTE{"
              + "dataBuffer=" + ArrayUtil.toHexadecimal(dataBuffer.array())
              + "}";
    }
  }

  /**
   * Get an {@code Integer} from the TYPE_VARBYTE.
   *
   * @return TYPE_VARBYTE as {@code Integer} or {@code 0} if value is SQL {@code NULL}.
   */
  public Integer asInt() {
    if (dataBuffer != null) {
      return dataBuffer.getInt(0);
    } else {
      return 0;
    }
  }

  /**
   * Get an {@code String} from the TYPE_VARBYTE.
   *
   * @return TYPE_VARBYTE as {@code String} or {@code null} if value is SQL {@code NULL}.
   */
  public String asString() {
    try {
      if (dataBuffer != null) {
        return new String(dataBuffer.array(), charsetName);
      } else {
        return null;
      }
    } catch (UnsupportedEncodingException ex) {
      Logger.getLogger(TYPE_VARBYTE.class.getName()).log(Level.SEVERE, null, ex);
      return null;
    }
  }

  /**
   * Get an {@code Float} from the TYPE_VARBYTE.
   *
   * @return TYPE_VARBYTE as {@code Float} or {@code 0} if value is SQL {@code NULL}.
   */
  public Float asFloat() {
    if (dataBuffer != null) {
      return dataBuffer.getFloat(0);
    } else {
      return 0.0f;
    }
  }

  /**
   * Get an {@code Double} from the TYPE_VARBYTE.
   *
   * @return TYPE_VARBYTE as {@code Double} or {@code 0} if value is SQL {@code NULL}.
   */
  public Double asDouble() {
    if (dataBuffer != null) {
      return dataBuffer.getDouble(0);
    } else {
      return 0.0;
    }
  }

  /**
   * Get an {@code java.sql.Date} from the TYPE_VARBYTE.
   *
   * @return TYPE_VARBYTE as {@code java.sql.Date} or {@code null} if value is SQL {@code NULL}.
   */
  public Date asDate() {
    if (dataBuffer != null) {
      long days = dataBuffer.getInt(0) - 25567;
      long seconds = dataBuffer.getInt(4) / 300;
      System.out.println("days=" + days);
      System.out.println("seconds=" + seconds);
      return new Date((long) ((long) (days * 86400000) + ((long) (seconds * 1000))));

      // 25567 = DATEDIFF(day, '1900-01-01', '1970-01-01')
      // 86400000 = number of milliseconds within a day.
    } else {
      return null;
    }
  }

  /**
   * Check if this TYPE_VARBYTE represents SQL {@code NULL}.
   *
   * @return {@code true} if is SQL {@code NULL}, otherwise {@code false}.
   */
  public boolean isNull() {
    return dataBuffer == null;
  }
}
